# Copyright 2011-2012 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Refer to the README and COPYING files for full details of the license
#

import sys
import os
import re
import traceback
import pipes
import pwd
import time
import logging
import threading
from xml.sax.saxutils import escape
import glob
import shutil

import libvirt
import selinux

from vdsm import constants
from vdsm import utils
from storage.misc import execCmd
import neterrors as ne
from vdsm import define
from vdsm import netinfo
from vdsm import libvirtconnection

CONNECTIVITY_TIMEOUT_DEFAULT = 4
MAX_VLAN_ID = 4094
MAX_BRIDGE_NAME_LEN = 15
ILLEGAL_BRIDGE_CHARS = frozenset(':. \t')


class ConfigNetworkError(Exception):
    def __init__(self, errCode, message):
        self.errCode = errCode
        self.message = message
        Exception.__init__(self, self.errCode, self.message)


def ifdown(iface):
    "Bring down an interface"
    rc, out, err = execCmd([constants.EXT_IFDOWN, iface], raw=True)
    return rc


def ifup(iface, async=False):
    "Bring up an interface"
    _ifup = lambda netIf: execCmd([constants.EXT_IFUP, netIf], raw=True)

    if async:
        # wait for dhcp in another thread,
        # so vdsm won't get stuck (BZ#498940)
        t = threading.Thread(target=_ifup, name='ifup-waiting-on-dhcp',
                             args=(iface,))
        t.daemon = True
        t.start()
    else:
        rc, out, err = _ifup(iface)
        return rc


def ifaceUsers(iface):
    "Returns a list of entities using the interface"
    _netinfo = netinfo.NetInfo()
    users = set()
    for n, ndict in _netinfo.networks.iteritems():
        if ndict['bridged'] and iface in ndict['ports']:
            users.add(n)
        elif not ndict['bridged'] and iface == ndict['iface']:
            users.add(n)
    for b, bdict in _netinfo.bondings.iteritems():
        if iface in bdict['slaves']:
            users.add(b)
    for v, vdict in _netinfo.vlans.iteritems():
        if iface == vdict['iface']:
            users.add(v)
    return users


def nicOtherUsers(bridge, vlan, bonding, nic):
    """
    Returns a list of interfaces using a nic,
    other than the specified one (used for validation)
    """
    if bonding:
        owner = bonding
    elif vlan:
        owner = nic + '.' + vlan
    else:
        owner = bridge
    users = ifaceUsers(nic)
    if bonding:
        users.update(bondingOtherUsers(bridge, vlan, bonding))
    users.discard(owner)
    return users


def bondingOtherUsers(bridge, vlan, bonding):
    """
    Return a list of nics/interfaces using a bonding,
    other than the specified one (used for validation)
    """
    if vlan:
        owner = bonding + '.' + vlan
    else:
        owner = bridge
    users = ifaceUsers(bonding)
    users.discard(owner)
    return users

# This function must respect the order used in:
#
#   /etc/rc.d/init.d/network
#
#   echo -e "ifcfg-eth0\nifcfg-eth1" \
#       | sed -e '/ifcfg-[A-Za-z0-9#\._-]\+$/ { s/^ifcfg-//g;s/[0-9]/ &/}' \
#       | sort -k 1,1 -k 2n
#
# Relevant cases:
#   nicSort(["p33p2", "p33p10"]) => ["p33p10", "p33p2"]
#   nicSort(["p331", "p33p1"]) => ["p33p1", "p331"]
#


def nicSort(nics):
    "Return a list of nics/interfaces ordered by name"

    nics_list = []
    nics_rexp = re.compile("^(\D*)(\d*)(.*)$")

    for nic_name in nics:
        nic_sre = nics_rexp.match(nic_name)
        prefix, stridx, postfix = nic_sre.groups((nic_name, "0", ""))

        try:
            intidx = int(stridx)
        except ValueError:
            intidx = 0

        nics_list.append((prefix, intidx, stridx + postfix))

    return [x + z for x, y, z in sorted(nics_list)]


class ConfigWriter(object):
    NET_CONF_PREF = netinfo.NET_CONF_DIR + 'ifcfg-'
    CONFFILE_HEADER = '# automatically generated by vdsm'
    DELETED_HEADER = '# original file did not exist'

    def __init__(self):
        self._backups = {}
        self._networksBackups = {}

    @staticmethod
    def _removeFile(filename):
        """Remove file, umounting ovirt config files if needed."""

        mounts = open('/proc/mounts').read()
        if ' /config ext3' in mounts and ' %s ext3' % filename in mounts:
            execCmd([constants.EXT_UMOUNT, '-n', filename])
        utils.rmFile(filename)
        logging.debug("Removed file %s", filename)

    def _createNetwork(self, netXml):
        conn = libvirtconnection.get()
        net = conn.networkDefineXML(netXml)
        net.create()
        net.setAutostart(1)

    def createLibvirtNetwork(self, network, bridged=True, iface=None,
                             skipBackup=False):
        netName = netinfo.LIBVIRT_NET_PREFIX + network
        if bridged:
            netXml = '''<network><name>%s</name><forward mode='bridge'/>
                        <bridge name='%s'/></network>''' % (escape(netName),
                                                            escape(network))
        else:
            netXml = '''<network><name>%s</name><forward mode='passthrough'>
                        <interface dev='%s'/></forward></network>''' % \
                                            (escape(netName), escape(iface))
        if not skipBackup:
            self._networkBackup(network)
        self._createNetwork(netXml)

    def _removeNetwork(self, network):
        netName = netinfo.LIBVIRT_NET_PREFIX + network
        conn = libvirtconnection.get()

        net = conn.networkLookupByName(netName)
        if net.isActive():
            net.destroy()
        if net.isPersistent():
            net.undefine()

    def removeLibvirtNetwork(self, network, skipBackup=False):
        if not skipBackup:
            self._networkBackup(network)
        self._removeNetwork(network)

    @classmethod
    def getLibvirtNetwork(cls, network):
        netName = netinfo.LIBVIRT_NET_PREFIX + network
        conn = libvirtconnection.get()
        try:
            net = conn.networkLookupByName(netName)
            return net.XMLDesc(0)
        except libvirt.libvirtError, e:
            if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK:
                return

            raise

    @classmethod
    def writeBackupFile(cls, dirName, fileName, content):
        backup = os.path.join(dirName, fileName)
        if os.path.exists(backup):
            # original copy already backed up
            return

        vdsm_uid = pwd.getpwnam('vdsm').pw_uid

        # make directory (if it doesn't exist) and assign it to vdsm
        if not os.path.exists(dirName):
            os.makedirs(dirName)
        os.chown(dirName, vdsm_uid, 0)

        open(backup, 'w').write(content)
        os.chown(backup, vdsm_uid, 0)
        logging.debug("Persistently backed up %s "
                      "(until next 'set safe config')", backup)

    def _networkBackup(self, network):
        self._atomicNetworkBackup(network)
        self._persistentNetworkBackup(network)

    def _atomicNetworkBackup(self, network):
        """ In-memory backup libvirt networks """
        if network not in self._networksBackups:
            self._networksBackups[network] = self.getLibvirtNetwork(network)
            logging.debug("Backed up %s", network)

    @classmethod
    def _persistentNetworkBackup(cls, network):
        """ Persistently backup libvirt networks """
        content = cls.getLibvirtNetwork(network)
        if not content:
            # For non-exists networks use predefined header
            content = cls.DELETED_HEADER + '\n'
        logging.debug("backing up network %s: %s", network, content)

        cls.writeBackupFile(netinfo.NET_LOGICALNET_CONF_BACK_DIR,
                             network, content)

    def restoreAtomicNetworkBackup(self):
        logging.info("Rolling back logical networks configuration "
                     "(restoring atomic logical networks backup)")
        for network, content in self._networksBackups.iteritems():
            # Networks with content None should be removed.
            # Networks with real content should be recreated.
            # To avoid libvirt errors during recreation we need
            # to remove the old network first
            try:
                self._removeNetwork(network)
            except libvirt.libvirtError, e:
                if e.get_error_code() == libvirt.VIR_ERR_NO_NETWORK:
                    pass

            if content:
                self._createNetwork(content)

            logging.info('Restored %s', network)

    def _backup(self, filename):
        self._atomicBackup(filename)
        self._persistentBackup(filename)

    def _atomicBackup(self, filename):
        """
        Backs up configuration to memory,
        for a later rollback in case of error.
        """

        if filename not in self._backups:
            try:
                self._backups[filename] = open(filename).read()
                logging.debug("Backed up %s", filename)
            except IOError, e:
                if e.errno == os.errno.ENOENT:
                    self._backups[filename] = None
                else:
                    raise

    def restoreAtomicBackup(self):
        logging.info("Rolling back configuration (restoring atomic backup)")
        for confFile, content in self._backups.iteritems():
            if content is None:
                utils.rmFile(confFile)
                logging.debug(
                        'Removing empty configuration backup %s', confFile)
            else:
                open(confFile, 'w').write(content)
            logging.info('Restored %s', confFile)

    @classmethod
    def _persistentBackup(cls, filename):
        """ Persistently backup ifcfg-* config files """
        if os.path.exists('/usr/libexec/ovirt-functions'):
            execCmd([constants.EXT_SH, '/usr/libexec/ovirt-functions',
                             'unmount_config', filename])
            logging.debug("unmounted %s using ovirt", filename)

        (dummy, basename) = os.path.split(filename)
        if os.path.exists(filename):
            content = open(filename).read()
        else:
            # For non-exists ifcfg-* file use predefined header
            content = cls.DELETED_HEADER + '\n'
        logging.debug("backing up %s: %s", basename, content)

        cls.writeBackupFile(netinfo.NET_CONF_BACK_DIR, basename, content)

    def restorePersistentBackup(self):
        """Restore network config to last known 'safe' state"""

        self.loadBackups()
        self.restoreBackups()
        self.clearBackups()

    def _loadBackupFiles(self, loadDir, restoreDir=None):
        for fpath in glob.iglob(loadDir + '/*'):
            if not os.path.isfile(fpath):
                continue

            content = open(fpath).read()
            if content.startswith(self.DELETED_HEADER):
                content = None

            basename = os.path.basename(fpath)
            if restoreDir:
                self._backups[os.path.join(restoreDir, basename)] = content
            else:
                self._networksBackups[basename] = content

            logging.info('Loaded %s', fpath)

    def loadBackups(self):
        """ Load persistent backups into memory """
        # Load logical networks
        self._loadBackupFiles(netinfo.NET_LOGICALNET_CONF_BACK_DIR)
        # Load config files
        self._loadBackupFiles(netinfo.NET_CONF_BACK_DIR, netinfo.NET_CONF_DIR)

    def restoreBackups(self):
        """ Restore network backups """
        if not self._backups and not self._networksBackups:
            return

        execCmd(['/etc/init.d/network', 'stop'])

        self.restoreAtomicNetworkBackup()
        self.restoreAtomicBackup()

        execCmd(['/etc/init.d/network', 'start'])

    @classmethod
    def clearBackups(cls):
        """ Clear backup files """
        shutil.rmtree(netinfo.NET_CONF_BACK_DIR, ignore_errors=True)

    def writeConfFile(self, fileName, configuration):
        '''Backs up the previous contents of the file referenced by fileName
        writes the new configuration and sets the specified access mode.'''
        self._backup(fileName)
        open(fileName, 'w').write(configuration)
        os.chmod(fileName, 0664)
        try:
            selinux.restorecon(fileName)
        except:
            logging.debug('ignoring restorecon error in case '
                                      'SElinux is disabled', exc_info=True)

    def _createConfFile(self, conf, name, ipaddr=None, netmask=None,
                        gateway=None, bootproto=None, mtu=None, onboot='yes',
                        **kwargs):
        """ Create ifcfg-* file with proper fields per device """

        cfg = """DEVICE=%s\nONBOOT=%s\n""" % (pipes.quote(name),
                                              pipes.quote(onboot))
        cfg += conf
        if ipaddr:
            cfg = cfg + 'IPADDR=%s\nNETMASK=%s\n' % (pipes.quote(ipaddr),
                                                     pipes.quote(netmask))
            if gateway:
                cfg = cfg + 'GATEWAY=%s\n' % pipes.quote(gateway)
            # According to manual the BOOTPROTO=none should be set
            # for static IP
            cfg = cfg + 'BOOTPROTO=none\n'
        else:
            if bootproto:
                cfg = cfg + 'BOOTPROTO=%s\n' % pipes.quote(bootproto)
        if mtu:
            cfg = cfg + 'MTU=%d\n' % mtu
        cfg += 'NM_CONTROLLED=no\n'
        BLACKLIST = ['TYPE', 'NAME', 'DEVICE', 'bondingOptions',
                     'force', 'blockingdhcp',
                     'connectivityCheck', 'connectivityTimeout',
                     'implicitBonding']
        for k in set(kwargs.keys()).difference(set(BLACKLIST)):
            if re.match('^[a-zA-Z_]\w*$', k):
                cfg += '%s=%s\n' % (k.upper(), pipes.quote(kwargs[k]))
            else:
                logging.debug('ignoring variable %s', k)

        self.writeConfFile(self.NET_CONF_PREF + name, cfg)

    def addBridge(self, name, ipaddr=None, netmask=None, mtu=None,
            gateway=None, bootproto=None, delay='0', onboot='yes', **kwargs):
        """ Create ifcfg-* file with proper fields for bridge """
        conf = 'TYPE=Bridge\nDELAY=%s\n' % pipes.quote(delay)
        self._createConfFile(conf, name, ipaddr, netmask, gateway,
                             bootproto, mtu, onboot, **kwargs)

    def addVlan(self, vlanId, iface, network, mtu=None, bridged=True,
                ipaddr=None, netmask=None, gateway=None, bootproto=None,
                onboot='yes', **kwargs):
        """ Create ifcfg-* file with proper fields for VLAN """
        name = '%s.%s' % (pipes.quote(iface), vlanId)
        conf = 'VLAN=yes\n'
        if bridged:
            conf += 'BRIDGE=%s\n' % pipes.quote(network)

        self._createConfFile(conf, name, ipaddr, netmask, gateway,
                             bootproto, mtu, onboot, **kwargs)

    def addBonding(self, bonding, bridge=None, bondingOptions=None, mtu=None,
                   ipaddr=None, netmask=None, gateway=None, bootproto=None,
                   onboot='yes', **kwargs):
        """ Create ifcfg-* file with proper fields for bond """
        if not bondingOptions:
            bondingOptions = 'mode=802.3ad miimon=150'

        conf = 'BONDING_OPTS=%s\n' % pipes.quote(bondingOptions or '')
        if bridge:
            conf += 'BRIDGE=%s\n' % pipes.quote(bridge)

        if ifaceUsers(bonding):
            confParams = netinfo.getIfaceCfg(bonding)
            if not ipaddr:
                ipaddr = confParams.get('IPADDR', None)
                netmask = confParams.get('NETMASK', None)
                gateway = confParams.get('GATEWAY', None)
            if not mtu:
                mtu = confParams.get('MTU', None)
                if mtu:
                    mtu = int(mtu)

        self._createConfFile(conf, bonding, ipaddr, netmask, gateway,
                             bootproto, mtu, onboot, **kwargs)

        # create the bonding device to avoid initscripts noise
        bondMastersPath = '/sys/class/net/bonding_masters'
        if bonding not in open(bondMastersPath).read().split():
            open(bondMastersPath, 'w').write('+%s\n' % bonding)

    def addNic(self, nic, bonding=None, bridge=None, mtu=None,
               ipaddr=None, netmask=None, gateway=None, bootproto=None,
               onboot='yes', **kwargs):
        """ Create ifcfg-* file with proper fields for NIC """
        _netinfo = netinfo.NetInfo()
        hwaddr = _netinfo.nics[nic].get('permhwaddr') or \
                 _netinfo.nics[nic]['hwaddr']

        conf = 'HWADDR=%s\n' % pipes.quote(hwaddr)
        if bridge:
            conf += 'BRIDGE=%s\n' % pipes.quote(bridge)
        if bonding:
            conf += 'MASTER=%s\nSLAVE=yes\n' % pipes.quote(bonding)

        if ifaceUsers(nic):
            confParams = netinfo.getIfaceCfg(nic)
            if not ipaddr:
                ipaddr = confParams.get('IPADDR', None)
                netmask = confParams.get('NETMASK', None)
                gateway = confParams.get('GATEWAY', None)
            if not mtu:
                mtu = confParams.get('MTU', None)
                if mtu:
                    mtu = int(mtu)

        self._createConfFile(conf, nic, ipaddr, netmask, gateway,
                             bootproto, mtu, onboot, **kwargs)

    def removeNic(self, nic):
        cf = self.NET_CONF_PREF + nic
        self._backup(cf)
        try:
            hwlines = [line for line in open(cf).readlines()
                       if line.startswith('HWADDR=')]
            l = ['DEVICE=%s\n' % nic, 'ONBOOT=yes\n', 'MTU=1500\n'] + hwlines
            open(cf, 'w').writelines(l)
        except IOError:
            pass

    def removeVlan(self, vlan, iface):
        vlandev = iface + '.' + vlan
        ifdown(vlandev)
        execCmd([constants.EXT_IPROUTE, 'link', 'del', vlandev])
        self._backup(self.NET_CONF_PREF + iface + '.' + vlan)
        self._removeFile(self.NET_CONF_PREF + iface + '.' + vlan)

    def removeBonding(self, bonding):
        self._backup(self.NET_CONF_PREF + bonding)
        self._removeFile(self.NET_CONF_PREF + bonding)

    def removeBridge(self, bridge):
        ifdown(bridge)
        execCmd([constants.EXT_BRCTL, 'delbr', bridge])
        self._backup(self.NET_CONF_PREF + bridge)
        self._removeFile(self.NET_CONF_PREF + bridge)

    def _getConfigValue(self, conffile, entry):
        """
        Get value from network configuration file

        :param entry: entry to look for (entry=value)
        :type entry: string

        :returns: value for entry (or None)
        :rtype: string

        Search for entry in conffile and return
        its value or None if not found
        """
        with open(conffile) as f:
            entries = [line for line in f.readlines()
                       if line.startswith(entry + '=')]
        if len(entries) != 0:
            value = entries[0].split('=', 1)[1]
            return value.strip()
        return None

    def _updateConfigValue(self, conffile, entry, value, delete=False):
        """
        Set value for network configuration file

        :param entry: entry to update (entry=value)
        :type entry: string

        :param value: value to update (entry=value)
        :type value: string

        :param delete: delete entry
        :type delete: boolean

        Search for entry in conffile and return
        its value or None if not found,
        if delete is True the entry will be deleted from
        the configuration file
        """
        with open(conffile) as f:
            entries = [line for line in f.readlines()
                       if not line.startswith(entry + '=')]

        if not delete:
            entries.append('\n' + entry + '=' + value)

        self._backup(conffile)
        with open(conffile, 'w') as f:
            f.writelines(entries)
            f.close()

    def getMaxMtu(self, nics, mtu):
        """
        Get the max MTU value from configuration/parameter

        :param nics: list of nics
        :type nics: list

        :param mtu: mtu value
        :type mtu: integer

        getMaxMtu return the highest value in a connection tree,
        it check if a vlan, bond that have a higher mtu value
        """
        for nic in nics:
            cf = self.NET_CONF_PREF + nic
            mtuval = self._getConfigValue(cf, 'MTU')
            if not mtuval is None:
                mtuval = int(mtuval)
                if mtuval > mtu:
                    mtu = mtuval
        return mtu

    def setNewMtu(self, network, bridged):
        """
        Set new MTU value to network and its interfaces

        :param network: network name
        :type network: string
        :param bridged: network type (bridged or bridgeless)
        :type bridged: bool

        Update MTU to devices (vlans, bonds and nics)
        or added a new value
        """
        _netinfo = netinfo.NetInfo()
        currmtu = None
        if bridged:
            cf = self.NET_CONF_PREF + network
            currmtu = self._getConfigValue(cf, 'MTU')
            if currmtu:
                currmtu = int(currmtu)
            else:
                # Optimization: if network hasn't custom MTU, do nothing
                return

        nics, delvlan, bonding = \
            _netinfo.getNicsVlanAndBondingForNetwork(network)
        if delvlan is None:
            return

        iface = bonding if bonding else nics[0]
        vlans = _netinfo.getVlansForIface(iface)

        newmtu = None
        for vlan in vlans:
            cf = self.NET_CONF_PREF + iface + '.' + vlan
            mtu = self._getConfigValue(cf, 'MTU')
            if mtu:
                mtu = int(mtu)

            if vlan == delvlan:
                # For VLANed bridgeless networks use MTU of delvlan
                # as current MTU
                if not bridged and mtu:
                    currmtu = mtu
                continue

            newmtu = max(newmtu, mtu)

        # Optimization: if network hasn't custom MTU (currmtu), do nothing
        if currmtu and newmtu != currmtu:
            if bonding:
                cf = self.NET_CONF_PREF + bonding
                self._updateConfigValue(cf, 'MTU', str(newmtu), newmtu is None)
                slaves = netinfo.slaves(bonding)
                for slave in slaves:
                    cf = self.NET_CONF_PREF + slave
                    self._updateConfigValue(cf, 'MTU', str(newmtu),
                                            newmtu is None)
            else:
                cf = self.NET_CONF_PREF + nics[0]
                self._updateConfigValue(cf, 'MTU', str(newmtu), newmtu is None)


def isBridgeNameValid(bridgeName):
    return (bridgeName and len(bridgeName) <= MAX_BRIDGE_NAME_LEN and
            len(set(bridgeName) & ILLEGAL_BRIDGE_CHARS) == 0 and
            not bridgeName.startswith('-'))


def validateBridgeName(bridgeName):
    if not isBridgeNameValid(bridgeName):
        raise ConfigNetworkError(ne.ERR_BAD_BRIDGE,
                                 "Bridge name isn't valid: %r" % bridgeName)


def _validateIpAddress(address):
    try:
        parts = address.split(".")
        if len(parts) != 4:
            return False
        for item in parts:
            if not 0 <= int(item) <= 255:
                return False
    except ValueError:
        return False
    return True


def validateIpAddress(ipAddr):
    if not _validateIpAddress(ipAddr):
        raise ConfigNetworkError(ne.ERR_BAD_ADDR,
                                 "Bad IP address: %r" % ipAddr)


def validateNetmask(netmask):
    if not _validateIpAddress(netmask):
        raise ConfigNetworkError(ne.ERR_BAD_ADDR,
                                 "Bad netmask: %r" % netmask)


def validateGateway(gateway):
    if not _validateIpAddress(gateway):
        raise ConfigNetworkError(ne.ERR_BAD_ADDR,
                                 "Bad gateway: %r" % gateway)


def validateBondingName(bonding):
    if not re.match('^bond[0-9]+$', bonding):
        raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                                 '%r is not a valid bonding device name' %
                                 bonding)


def validateBondingOptions(bonding, bondingOptions):
    'Example: BONDING_OPTS="mode=802.3ad miimon=150"'
    try:
        for option in bondingOptions.split():
            key, value = option.split('=')
            if not os.path.exists(
                    '/sys/class/net/%(bonding)s/bonding/%(key)s' % locals()):
                raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                        "%r is not a valid bonding option" % key)
    except ValueError:
        raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                "Error parsing bonding options: %r" % bondingOptions)


def validateVlanId(vlan):
    try:
        if not 0 <= int(vlan) <= MAX_VLAN_ID:
            raise ConfigNetworkError(
                ne.ERR_BAD_VLAN, 'vlan id out of range: %r, must be 0..%s' %
                (vlan, MAX_VLAN_ID))
    except ValueError:
        raise ConfigNetworkError(ne.ERR_BAD_VLAN, 'vlan id must be a number')


def _validateInterNetworkCompatibility(ni, vlan, iface, bridged):
    """
    Verify network compatibility with other networks on iface (bond/nic).

    Only following combinations allowed:
        - single non-VLANed bridged network
        - multiple VLANed networks (bridged/bridgeless) with only one
          non-VLANed bridgeless network
    """
    def _validateNoDirectNet(ifaces):
        # validate that none of the ifaces
        # is a non-VLANed network over our iface
        for (iface_net, iface_vlan) in ifaces:
            if iface_vlan is None:
                raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                            "interface %r already member of network %r" %
                            (iface, iface_net))

    ifaces_bridgeless = tuple(ni.getBridgelessNetworksAndVlansForIface(iface))
    ifaces_bridged = tuple(ni.getBridgedNetworksAndVlansForIface(iface))

    # If non-VLANed bridged network exists
    # we can't add nothing else
    _validateNoDirectNet(ifaces_bridged)

    # Multiple VLANed networks (bridged/bridgeless) with only one
    # non-VLANed bridgeless network permited
    if not vlan:
        # Want to add non-VLANed bridgeless network,
        # check whether interface already has such network.
        # Only one non-VLANed bridgeless network permited
        if not bridged:
            _validateNoDirectNet(ifaces_bridgeless)
        # Want to add non-VLANed bridged network,
        # check whether interface is empty
        elif ifaces_bridged or ifaces_bridgeless:
            raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                        "interface %r already has networks" %
                        (iface))


def _addNetworkValidation(_netinfo, network, vlan, bonding, nics, ipaddr,
                          netmask, gateway, bondingOptions, bridged=True,
                          implicitBonding=False, **options):
    # The (relatively) new setupNetwork verb allows to specify a network on
    # top of an existing bonding device. The nics of this bonds are taken
    # implictly from current host configuration
    if bonding and implicitBonding:
        pass
    elif (vlan or bonding) and not nics:
        raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                'vlan/bonding definition requires nics. got: %r' % (nics,))

    # Check bridge
    if bridged:
        validateBridgeName(network)

    if network in _netinfo.networks:
        raise ConfigNetworkError(ne.ERR_USED_BRIDGE, 'Network already exists')

    # Check vlan
    if vlan:
        validateVlanId(vlan)

    # Check ip, netmask, gateway
    if ipaddr:
        if not netmask:
            raise ConfigNetworkError(ne.ERR_BAD_ADDR,
                        "Must specify netmask to configure ip for network")
        validateIpAddress(ipaddr)
        validateNetmask(netmask)
        if gateway:
            validateGateway(gateway)
    else:
        if netmask or gateway:
            raise ConfigNetworkError(ne.ERR_BAD_ADDR,
                        "Specified netmask or gateway but not ip")

    # Check bonding
    if bonding:
        validateBondingName(bonding)
        if bondingOptions:
            validateBondingOptions(bonding, bondingOptions)

        _validateInterNetworkCompatibility(_netinfo, vlan, bonding, bridged)
    elif bondingOptions:
        raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                    "Bonding options specified without bonding")
    elif len(nics) > 1:
        raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                    "Multiple nics require a bonding device")

    # Check nics
    for nic in nics:
        if nic not in _netinfo.nics:
            raise ConfigNetworkError(ne.ERR_BAD_NIC, "unknown nic: %r" % nic)

        # Make sure nics don't have a different bonding
        # still relevant if bonding is None
        bondingForNics = _netinfo.getBondingForNic(nic)
        if bondingForNics and bondingForNics != bonding:
            raise ConfigNetworkError(ne.ERR_USED_NIC,
                                "nic %s already enslaved to %s" %
                                (nic, bondingForNics))

        # Make sure nics don't used by vlans if bond requested
        if bonding:
            vlansForNic = tuple(_netinfo.getVlansForIface(nic))
            if vlansForNic:
                raise ConfigNetworkError(ne.ERR_USED_NIC,
                                    "nic %s already used by vlans %s" %
                                    (nic, vlansForNic))
            networksForNic = tuple(_netinfo.getNetworksForIface(nic))
            if networksForNic:
                raise ConfigNetworkError(ne.ERR_USED_NIC,
                                    "nic %s already used by networks %s" %
                                    (nic, networksForNic))
        else:
            _validateInterNetworkCompatibility(_netinfo, vlan, nic, bridged)


def addNetwork(network, vlan=None, bonding=None, nics=None, ipaddr=None,
               netmask=None, mtu=None, gateway=None, force=False,
               configWriter=None, bondingOptions=None, bridged=True,
               **options):
    nics = nics or ()
    _netinfo = netinfo.NetInfo()
    bridged = utils.tobool(bridged)

    if mtu:
        mtu = int(mtu)

    prefix = options.get('prefix')
    if prefix is not None:
        if netmask is None:
            netmask = netinfo.prefix2netmask(int(prefix))
            del options['prefix']
        else:
            raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                                     'Both PREFIX and NETMASK supplied')

    # Validation
    if not utils.tobool(force):
        logging.debug('validating network...')
        _addNetworkValidation(_netinfo, network=network,
              vlan=vlan, bonding=bonding, nics=nics, ipaddr=ipaddr,
              netmask=netmask, gateway=gateway, bondingOptions=bondingOptions,
              bridged=bridged, **options)

    logging.info("Adding network %s with vlan=%s, bonding=%s, nics=%s,"
                 " bondingOptions=%s, mtu=%s, bridged=%s, options=%s",
                 network, vlan, bonding, nics, bondingOptions,
                 mtu, bridged, options)

    if configWriter is None:
        configWriter = ConfigWriter()

    prevmtu = None
    if mtu:
        prevmtu = configWriter.getMaxMtu(nics, mtu)

    nic = nics[0] if nics else None
    iface = bonding or nic
    blockingDhcp = utils.tobool(options.get('blockingdhcp'))

    # take down nics that need to be changed
    vlanedIfaces = [v['iface'] for v in _netinfo.vlans.values()]
    if bonding not in vlanedIfaces:
        for nic in nics:
            if nic not in vlanedIfaces:
                ifdown(nic)

    if bridged:
        configWriter.addBridge(network, ipaddr=ipaddr, netmask=netmask,
                               mtu=mtu, gateway=gateway, **options)
        ifdown(network)
        # We need to define (if requested) ip, mask & gateway on ifcfg-*
        # only on most top device according to following order:
        # bridge -> vlan -> bond -> nic
        # For lower level devices we should ignore it.
        # reset ip, netmask, gateway and bootproto for lower level devices
        bridgeBootproto = options.get('bootproto')
        ipaddr = netmask = gateway = options['bootproto'] = None

    # For VLAN we should attach bridge only to the VLAN device
    # rather than to underlying NICs or bond
    brName = network if bridged else None
    bridgeForNic = None if vlan else brName

    # We want to create config files (ifcfg-*) in top-down order
    # (bridge->vlan->bond->nic) to be able to handle IP/NETMASK
    # correctly for bridgeless networks
    if vlan:
        # don't ifup VLAN interface here, it should be done last,
        # after the bond and nic up
        configWriter.addVlan(vlan, iface, network=brName,
                             mtu=mtu, bridged=bridged,
                             ipaddr=ipaddr, netmask=netmask,
                             gateway=gateway, **options)
        iface += '.' + vlan
        vlanBootproto = options.get('bootproto')
        # reset ip, netmask, gateway and bootproto for lower level devices
        ipaddr = netmask = gateway = options['bootproto'] = None

    # First we need to prepare all conf files
    if bonding:
        configWriter.addBonding(bonding, bridge=bridgeForNic,
                                bondingOptions=bondingOptions,
                                mtu=max(prevmtu, mtu),
                                ipaddr=ipaddr, netmask=netmask,
                                gateway=gateway, **options)
        bondBootproto = options.get('bootproto')
        # reset ip, netmask, gateway and bootproto for lower level devices
        ipaddr = netmask = gateway = options['bootproto'] = None

    for nic in nics:
        configWriter.addNic(nic, bonding=bonding,
                             bridge=bridgeForNic if not bonding else None,
                             mtu=max(prevmtu, mtu),
                             ipaddr=ipaddr, netmask=netmask,
                             gateway=gateway, **options)

    # Now we can run ifup for all interfaces
    if bonding:
        ifup(bonding, bondBootproto == 'dhcp' and not blockingDhcp)

    # NICs must be activated in the same order of boot time
    # to expose the correct MAC address.
    for nic in nicSort(nics):
        ifup(nic, options.get('bootproto') == 'dhcp' and not blockingDhcp)

    # Now we can ifup VLAN interface, because bond and nic already up
    if vlan:
        ifup(iface, vlanBootproto == 'dhcp' and not blockingDhcp)

    if bridged:
        ifup(network, bridgeBootproto == 'dhcp' and not blockingDhcp)

    # add libvirt network
    configWriter.createLibvirtNetwork(network, bridged, iface)


def assertBridgeClean(bridge, vlan, bonding, nics):
    brifs = os.listdir('/sys/class/net/%s/brif/' % bridge)
    for nic in nics:
        try:
            brifs.remove(nic)
        except:
            pass
    if vlan:
        brif = (bonding or nics[0]) + '.' + vlan
    else:
        brif = bonding
    try:
        brifs.remove(brif)
    except:
        pass

    if brifs:
        raise ConfigNetworkError(ne.ERR_USED_BRIDGE,
                 'bridge %s has interfaces %s connected' % (bridge, brifs))


def showNetwork(network):
    _netinfo = netinfo.NetInfo()
    if network not in _netinfo.networks:
        print "Network %r doesn't exist" % network
        return

    bridged = _netinfo.networks[network]['bridged']
    print "Network %s(Bridged: %s):" % (network, bridged)

    nics, vlan, bonding = _netinfo.getNicsVlanAndBondingForNetwork(network)

    if bridged:
        ipaddr = _netinfo.networks[network]['addr']
        netmask = _netinfo.networks[network]['netmask']
        gateway = _netinfo.networks[network]['gateway']
        print "ipaddr=%s, netmask=%s, gateway=%s" % (ipaddr, netmask, gateway)
    else:
        iface = _netinfo.networks[network]['iface']
        ipaddr = _netinfo.nics[iface]['addr']
        netmask = _netinfo.nics[iface]['netmask']
        print "ipaddr=%s, netmask=%s" % (ipaddr, netmask)

    print "vlan=%s, bonding=%s, nics=%s" % (vlan, bonding, nics)


def listNetworks():
    _netinfo = netinfo.NetInfo()
    print "Networks:", _netinfo.networks.keys()
    print "Vlans:", _netinfo.vlans.keys()
    print "Nics:", _netinfo.nics.keys()
    print "Bondings:", _netinfo.bondings.keys()


def delNetwork(network, vlan=None, bonding=None, nics=None, force=False,
               configWriter=None, implicitBonding=True, **options):
    _netinfo = netinfo.NetInfo()

    if configWriter is None:
        configWriter = ConfigWriter()

    if network not in _netinfo.networks:
        logging.info("Network %r: doesn't exist in libvirt database", network)
        if network in netinfo.bridges():
            configWriter.removeBridge(network)
        else:
            raise ConfigNetworkError(ne.ERR_BAD_BRIDGE,
                    "Cannot delete network %r: It doesn't exist "
                    "in the system" % network)

        if vlan:
            configWriter.removeVlan(vlan, bonding or nics[0])

        return

    nics, vlan, bonding = _netinfo.getNicsVlanAndBondingForNetwork(network)
    bridged = _netinfo.networks[network]['bridged']

    logging.info("Removing network %s with vlan=%s, bonding=%s, nics=%s,"
                 "options=%s" % (network, vlan, bonding, nics, options))

    if not utils.tobool(force):
        if bonding:
            validateBondingName(bonding)
            if set(nics) != set(_netinfo.bondings[bonding]["slaves"]):
                raise ConfigNetworkError(ne.ERR_BAD_NIC,
                        "delNetwork: %s are not all nics enslaved to %s" %
                        (nics, bonding))
        if vlan:
            validateVlanId(vlan)
        if bridged:
            assertBridgeClean(network, vlan, bonding, nics)

    configWriter.setNewMtu(network=network, bridged=bridged)
    configWriter.removeLibvirtNetwork(network)

    # We need to gather NetInfo again to refresh networks info from libvirt.
    # The deleted bridge should never be up at this stage.
    if network in netinfo.NetInfo().networks:
        raise ConfigNetworkError(ne.ERR_USED_BRIDGE,
                "delNetwork: bridge %s still exists" % network)

    if network and bridged:
        configWriter.removeBridge(network)

    nic = nics[0] if nics else None
    iface = bonding if bonding else nic
    if iface:
        if vlan:
            configWriter.removeVlan(vlan, iface)
        else:
            cf = configWriter.NET_CONF_PREF + iface
            if not bridged:
                # When removing bridgeless non-VLANed network
                # we need to remove IP/NETMASK from the cfg file
                for key in ('IPADDR', 'NETMASK', 'GATEWAY', 'BOOTPROTO'):
                    configWriter._updateConfigValue(cf, key, '', True)
            else:
                # When removing bridged non-VLANed network
                # we need to remove BRIDGE from the cfg file
                configWriter._updateConfigValue(cf, 'BRIDGE', '', True)

    # The (relatively) new setupNetwork verb allows to remove a network
    # defined on top of an bonding device without break the bond itself.
    if implicitBonding:
        if bonding and not bondingOtherUsers(network, vlan, bonding):
            ifdown(bonding)
            configWriter.removeBonding(bonding)
            iface = None if bonding == iface else iface

        for nic in nics:
            if not nicOtherUsers(network, vlan, bonding, nic):
                ifdown(nic)
                configWriter.removeNic(nic)
                ifup(nic)
                iface = None if nic == iface else iface

    # Now we can restart changed interface
    if iface:
        ifdown(iface)
        ifup(iface)


def clientSeen(timeout):
    start = time.time()
    while timeout >= 0:
        if os.stat(constants.P_VDSM_CLIENT_LOG).st_mtime > start:
            return True
        time.sleep(1)
        timeout -= 1
    return False


def editNetwork(oldBridge, newBridge, vlan=None, bonding=None, nics=None,
                **options):
    configWriter = ConfigWriter()
    try:
        delNetwork(oldBridge, configWriter=configWriter, **options)
        addNetwork(newBridge, vlan=vlan, bonding=bonding, nics=nics,
                   configWriter=configWriter, **options)
    except:
        configWriter.restoreBackups()
        raise
    if utils.tobool(options.get('connectivityCheck', False)):
        if not clientSeen(int(options.get('connectivityTimeout',
                                          CONNECTIVITY_TIMEOUT_DEFAULT))):
            delNetwork(newBridge, force=True)
            configWriter.restoreBackups()
            return define.errCode['noConPeer']['status']['code']


def _validateNetworkSetup(networks={}, bondings={}):
    _netinfo = netinfo.NetInfo()

    for network, networkAttrs in networks.iteritems():
        if networkAttrs.get('remove', False):
            if set(networkAttrs) - set(['remove']):
                raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                          "Cannot specify any attribute when removing")

    for bonding, bondingAttrs in bondings.iteritems():
        validateBondingName(bonding)
        if 'options' in bondingAttrs:
            validateBondingOptions(bonding, bondingAttrs['options'])

        if bondingAttrs.get('remove', False):
            if bonding not in _netinfo.bondings:
                raise ConfigNetworkError(ne.ERR_BAD_BONDING,
                          'Cannot remove bonding %s: Doesn\'t exist' % bonding)
            continue

        nics = bondingAttrs.get('nics', None)
        if not nics:
            raise ConfigNetworkError(ne.ERR_BAD_PARAMS,
                                     "Must specify nics for bonding")
        if not set(nics).issubset(set(_netinfo.nics)):
            raise ConfigNetworkError(ne.ERR_BAD_NIC,
                                     "Unknown nics in: %r" % list(nics))


def _editBondings(bondings, configWriter):
    """ Add/Edit bond interface """
    logger = logging.getLogger("_editBondings")

    _netinfo = netinfo.NetInfo()

    for bond, bondAttrs in bondings.iteritems():
        logger.debug("Creating/Editing bond %s with attributes %s",
                     bond, bondAttrs)

        brNets = list(_netinfo.getBridgedNetworksForIface(bond))
        # Only one bridged-non-VLANed network allowed on same nic/bond
        bridge = brNets[0] if brNets else None

        mtu = None
        if bond in _netinfo.bondings:
            # Save MTU for future set on NICs
            confParams = netinfo.getIfaceCfg(bond)
            mtu = confParams.get('MTU', None)
            if mtu:
                mtu = int(mtu)

            ifdown(bond)
            # Take down all bond's NICs.
            for nic in _netinfo.getNicsForBonding(bond):
                ifdown(nic)
                configWriter.removeNic(nic)
                if nic not in bondAttrs['nics']:
                    ifup(nic)

        # Note! In case we have bridge up and connected to the bond
        # we will get error in log:
        #   (ifdown) bridge XXX is still up; can't delete it
        # But, we prefer this behaviour instead of taking bridge down
        # Anyway, we will not be able to take it down with connected VMs

        # First we need to prepare all conf files
        configWriter.addBonding(bond, bridge=bridge, mtu=mtu,
                                bondingOptions=bondAttrs.get('options', None))

        for nic in bondAttrs['nics']:
            configWriter.addNic(nic, bonding=bond, mtu=mtu)

        # Now we can run ifup for all interfaces
        ifup(bond)
        # NICs must be activated in the same order of boot time
        # to expose the correct MAC address.
        for nic in nicSort(bondAttrs['nics']):
            ifup(nic)


def _removeBondings(bondings, configWriter):
    """ Remove bond interface """
    logger = logging.getLogger("_removeBondings")

    _netinfo = netinfo.NetInfo()

    for bond, bondAttrs in bondings.items():
        if 'remove' in bondAttrs:
            nics = _netinfo.getNicsForBonding(bond)
            logger.debug("Removing bond %r with nics = %s", bond, nics)
            ifdown(bond)
            configWriter.removeBonding(bond)

            for nic in nics:
                ifdown(nic)
                configWriter.removeNic(nic)
                ifup(nic)

            del bondings[bond]


def setupNetworks(networks={}, bondings={}, **options):
    """Add/Edit/Remove configuration for networks and bondings.

    Params:
        networks - dict of key=network, value=attributes
            where 'attributes' is a dict with the following optional items:
                        vlan=<id>
                        bonding="<name>" | nic="<name>"
                        (bonding and nics are mutually exclusive)
                        ipaddr="<ip>"
                        netmask="<ip>"
                        gateway="<ip>"
                        bootproto="..."
                        delay="..."
                        onboot="yes"|"no"
                        (other options will be passed to the config file AS-IS)
                        -- OR --
                        remove=True (other attributes can't be specified)

        bondings - dict of key=bonding, value=attributes
            where 'attributes' is a dict with the following optional items:
                        nics=["<nic1>" , "<nic2>", ...]
                        options="<bonding-options>"
                        -- OR --
                        remove=True (other attributes can't be specified)

        options - dict of options, such as:
                        force=0|1
                        connectivityCheck=0|1
                        connectivityTimeout=<int>

    Notes:
        When you edit a network that is attached to a bonding, it's not
        necessary to re-specify the bonding (you need only to note
        the attachment in the network's attributes). Similarly, if you edit
        a bonding, it's not necessary to specify its networks.
    """
    logger = logging.getLogger("setupNetworks")

    try:
        _netinfo = netinfo.NetInfo()
        configWriter = ConfigWriter()
        networksAdded = set()
        # keep set netsWithNewBonds to be able remove
        # a new added network if connectivity check fail.
        # If a new network needs to be created on top of existing bond,
        # we will need to keep the bond on rollback flow,
        # else we will break the new created bond.
        netsWithNewBonds = set()

        logger.debug("Setting up network according to configuration: "
                     "networks:%r, bondings:%r, options:%r" % (networks,
                     bondings, options))

        force = options.get('force', False)
        if not utils.tobool(force):
            logging.debug("Validating configuration")
            _validateNetworkSetup(dict(networks), dict(bondings))

        logger.debug("Applying...")
        try:
            # Remove edited networks and networks with 'remove' attribute
            for network, networkAttrs in networks.items():
                if network in _netinfo.networks:
                    logger.debug("Removing network %r" % network)
                    delNetwork(network, configWriter=configWriter, force=force,
                               implicitBonding=False)
                    if 'remove' in networkAttrs:
                        del networks[network]
                else:
                    networksAdded.add(network)

            # Remove bonds with 'remove' attribute
            _removeBondings(bondings, configWriter)

            # Check whether bonds should be resized
            _editBondings(bondings, configWriter)

            # We need to use the newest host info
            _ni = netinfo.NetInfo()
            for network, networkAttrs in networks.iteritems():
                d = dict(networkAttrs)
                if 'bonding' in d:
                    # we may not receive any information
                    # about the bonding device if it is unchanged
                    # In this case check whether this bond exists
                    # on host and take its parameters
                    if bondings.get(d['bonding']):
                        d['nics'] = bondings[d['bonding']]['nics']
                        d['bondingOptions'] = \
                           bondings[d['bonding']].get('options', None)
                        # we create a new bond
                        if network in networksAdded:
                            netsWithNewBonds.add(network)
                    elif d['bonding'] in _ni.bondings:
                        logger.debug("Updating bond %r info", d['bonding'])
                        d['nics'] = _ni.bondings[d['bonding']]['slaves']
                        d['bondingOptions'] = \
                            _ni.bondings[d['bonding']]['cfg'].get(
                                                        'BONDING_OPTS', None)
                else:
                    d['nics'] = [d.pop('nic')]
                d['force'] = force

                logger.debug("Adding network %r" % network)
                addNetwork(network, configWriter=configWriter,
                           implicitBonding=True, **d)

            if utils.tobool(options.get('connectivityCheck', True)):
                logger.debug('Checking connectivity...')
                if not clientSeen(int(options.get('connectivityTimeout',
                                      CONNECTIVITY_TIMEOUT_DEFAULT))):
                    logger.info('Connectivity check failed, rolling back')
                    for network in networksAdded:
                        delNetwork(network, force=True,
                                   implicitBonding=network in netsWithNewBonds)
                    raise ConfigNetworkError(ne.ERR_LOST_CONNECTION,
                                             'connectivity check failed')
        except:
            configWriter.restoreBackups()
            raise

    except Exception, e:
        # SuperVdsm eats the error, so let's print it ourselves
        logger.error(e, exc_info=True)
        raise


def setSafeNetworkConfig():
    """Declare current network configuration as 'safe'"""
    execCmd([constants.EXT_VDSM_STORE_NET_CONFIG])


def usage():
    print """Usage:
    ./configNetwork.py add Network <attributes> <options>
                       edit oldNetwork newNetwork <attributes> <options>
                       del Network <options>
                       setup Network [None|attributes] \
[++ Network [None|attributes] [++ ...]] [:: <options>]

                       attributes = [vlan=...] [bonding=...] [nics=<nic1>,...]
                       options = [Force=<True|False>] [bridged=<True|False>]...
    """


def _parseKwargs(args):
    import API

    kwargs = dict(arg.split('=', 1) for arg in args)
    API.Global.translateNetOptionsToNew(kwargs)

    return kwargs


def main():
    if len(sys.argv) <= 1:
        usage()
        raise ConfigNetworkError(ne.ERR_BAD_PARAMS, "No action specified")
    if sys.argv[1] == 'list':
        listNetworks()
        return
    if len(sys.argv) <= 2:
        usage()
        raise ConfigNetworkError(ne.ERR_BAD_PARAMS, "No action specified")
    if sys.argv[1] == 'add':
        bridge = sys.argv[2]
        kwargs = _parseKwargs(sys.argv[3:])
        if 'nics' in kwargs:
            kwargs['nics'] = kwargs['nics'].split(',')
        addNetwork(bridge, **kwargs)
    elif sys.argv[1] == 'del':
        bridge = sys.argv[2]
        kwargs = _parseKwargs(sys.argv[3:])
        if 'nics' in kwargs:
            kwargs['nics'] = kwargs['nics'].split(',')
        delNetwork(bridge, **kwargs)
    elif sys.argv[1] == 'edit':
        oldBridge = sys.argv[2]
        newBridge = sys.argv[3]
        kwargs = _parseKwargs(sys.argv[4:])
        if 'nics' in kwargs:
            kwargs['nics'] = kwargs['nics'].split(',')
        editNetwork(oldBridge, newBridge, **kwargs)
    elif sys.argv[1] == 'setup':
        batchCommands, options = utils.listSplit(sys.argv[2:], '::', 1)
        d = {}
        for batchCommand in utils.listSplit(batchCommands, '++'):
            d[batchCommand[0]] = _parseKwargs(batchCommand[1:]) or None
        setupNetworks(d, **_parseKwargs(options))
    elif sys.argv[1] == 'show':
        bridge = sys.argv[2]
        kwargs = _parseKwargs(sys.argv[3:])
        showNetwork(bridge, **kwargs)
    else:
        usage()
        raise ConfigNetworkError(ne.ERR_BAD_PARAMS, "Unknown action specified")

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    try:
        main()
    except ConfigNetworkError, e:
        traceback.print_exc()
        print e.message
        sys.exit(e.errCode)
    sys.exit(0)
